<!--
 * @Author: Li Zengkun
 * @Date: 2022-09-05 19:37:29
 * @LastEditors: Li Zengkun
 * @LastEditTime: 2022-09-07 15:23:31
 * @Description: 
 * @Description: 
-->
<template>
  <div id="content" style="position: relative">
    <a-breadcrumb>
      <a-breadcrumb-item>底层原理</a-breadcrumb-item>
      <a-breadcrumb-item>Observe Data</a-breadcrumb-item>
      <a-breadcrumb-item>Vue 2</a-breadcrumb-item>
    </a-breadcrumb>
    <div class="article-content">
      <h1 class="title">
        Vue 2原理（利用 Object.defineProperty 数据劫持+发布订阅）
      </h1>
      <UserCard datetime="2022-09-05 19:37:29" />
      <h2>vue2.0 跟 vue3.0 的区别原理</h2>
      <ol>
        <li>结构: 2.0用Flow ,3.0用 TypeScript。</li>
        <li>性能: 3.0优化了Virtual Dom的算法。</li>
        <li>
          响应式原理:2.0用
          <a
            href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty"
            target="_blank"
            rel="noopener noreferrer"
            >Object.defineProperty</a
          >
          ,3.0用Proxy
        </li>
      </ol>
      <h2>Vue2.0和Vue3.0实现原理</h2>
      <ul>
        <li>
          <p><b>Vue 2.0:</b></p>
          <p>
            Vue2.0实现MVVM(双向数据绑定)的原理是通过
            <a
              href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty"
              target="_blank"
              rel="noopener noreferrer"
              >Object.defineProperty</a
            >
            来劫持各个属性的setter、getter，在数据变动时发布消息给订阅者，触发相应的监听回调。
          </p>
        </li>
        <li>
          <b>Vue 3.0:</b>
          <p>Vue 3.0 实现响应式基于ES6: Proxy、Reflect</p>
        </li>
      </ul>
      <div v-html="code1"></div>
      <pre><code class='language-javascript line-numbers'>{{code3}}</code></pre>
      <a-divider></a-divider>
      <h2>Vue 2.0 基本的响应式实现</h2>
      <h3>处理普通对象</h3>
      <pre><code class='language-javascript line-numbers'>{{code2}}</code></pre>
      <h3>处理复杂对象</h3>
      <pre><code class='language-javascript line-numbers'>{{code4}}</code></pre>
      <h3>实现响应式数组</h3>
      <p>
        vue中为了处理数组的响应式，改写 七个方法 push pop shift unshift splice
        sort reverse 静态方法都在Array.prototype 上 我们构造一个对象
        newArrayProto proto指向这个数组原型
      </p>
      <pre><code class='language-javascript line-numbers'>{{code5}}</code></pre>
    </div>
  </div>
   
</template>

<script setup>
import { ref, reactive, onMounted } from "vue";
import UserCard from "@/components/UserCard.vue";
import Prism from "prismjs";

onMounted(() => {
  Prism.highlightAll();
});

const code1 = ref(
  `<h2>Object.defineProperty</h2><p><span style="color: rgb(27, 27, 27); background-color: rgb(251, 233, 230); font-size: 16px;"><code><strong>Object.defineProperty()</strong></code></span><span style="color: rgb(27, 27, 27); background-color: rgb(255, 255, 255); font-size: 16px;"> 方法会直接在一个对象上定义一个新属性，或者修改一个对象的现有属性，并返回此对象。</span></p><h3><span style="color: rgb(27, 27, 27); background-color: rgb(255, 255, 255); font-size: 16px;">语法：</span></h3><pre><code class="language-javascript">Object.defineProperty(obj, prop, descriptor)</code></pre><h3>参数：</h3><ul><li><span style="background-color: rgb(251, 233, 230);"> obj </span>：要定义属性的对象。</li><li><span style="background-color: rgb(251, 233, 230);"> prop </span>：要定义或修改的属性的名称或 <a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Symbol" target=""><span style="background-color: var(--code-background-inline);"><code>Symbol</code></span></a> 。</li><li><span style="background-color: rgb(251, 233, 230);"> descriptor </span>：要定义或修改的属性描述符。</li></ul><h3>返回值：</h3><p><span style="color: rgb(27, 27, 27); background-color: rgb(255, 255, 255); font-size: 16px;">被传递给函数的对象。</span></p><h3><span style="color: rgb(27, 27, 27); background-color: rgb(255, 255, 255); font-size: 16px;">描述符：</span></h3><ul><li><span style="color: rgb(27, 27, 27); background-color: rgb(251, 233, 230); font-size: 16px;">configurable </span><span style="color: rgb(27, 27, 27); font-size: 16px;">：</span><span style="color: rgb(27, 27, 27); background-color: rgb(255, 255, 255); font-size: 16px;">当且仅当该属性的 </span>configurable<span style="color: rgb(27, 27, 27); background-color: rgb(255, 255, 255); font-size: 16px;"> 键值为 </span>true<span style="color: rgb(27, 27, 27); background-color: rgb(255, 255, 255); font-size: 16px;"> 时，该属性的描述符才能够被改变，同时该属性也能从对应的对象上被删除。 </span><span style="color: rgb(27, 27, 27); background-color: rgb(255, 255, 255); font-size: 16px;"><strong>默认为</strong></span><span style="color: rgb(27, 27, 27); background-color: rgb(255, 255, 255); font-size: 16px;"> </span><span style="color: rgb(27, 27, 27); background-color: rgb(255, 255, 255); font-size: 16px;"><code><strong>false</strong></code></span><span style="color: rgb(27, 27, 27); background-color: rgb(255, 255, 255); font-size: 16px;">。</span></li><li><span style="color: rgb(27, 27, 27); background-color: rgb(251, 233, 230); font-size: 16px;">enumerable </span><span style="color: rgb(27, 27, 27); background-color: rgb(255, 255, 255); font-size: 16px;">: </span><span style="color: rgb(27, 27, 27); font-size: 16px;">当且仅当该属性的 </span>enumerable<span style="color: rgb(27, 27, 27); font-size: 16px;"> 键值为 </span>true<span style="color: rgb(27, 27, 27); font-size: 16px;"> 时，该属性才会出现在对象的枚举属性中。 </span><span style="color: rgb(27, 27, 27); font-size: 16px;"><code><strong>默认为 false</strong></code></span><span style="color: rgb(27, 27, 27); font-size: 16px;">。</span></li><li><span style="color: rgb(27, 27, 27); font-size: 16px;">value : 该属性对应的值。可以是任何有效的 JavaScript 值（数值，对象，函数等）。 </span><span style="color: rgb(27, 27, 27); font-size: 16px;"><code><strong>默认为 undefined.</strong></code></span></li></ul><ul><li><span style="color: rgb(27, 27, 27); background-color: rgb(251, 233, 230); font-size: 16px;">writable </span><span style="color: rgb(27, 27, 27); background-color: rgb(255, 255, 255); font-size: 16px;">: &nbsp;</span><span style="color: rgb(27, 27, 27); font-size: 16px;">当且仅当该属性的 </span>writable<span style="color: rgb(27, 27, 27); font-size: 16px;"> 键值为 </span>true<span style="color: rgb(27, 27, 27); font-size: 16px;"> 时，属性的值，也就是上面的 </span>value<span style="color: rgb(27, 27, 27); font-size: 16px;">，才能被</span><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators#assignment_operators" target="" style="text-align: start;"><code>赋值运算符</code> </a><span style="color: rgb(27, 27, 27); font-size: 16px;">改变。 </span><span style="color: rgb(27, 27, 27); font-size: 16px;"><code><strong>默认为 false。</strong></code></span></li><li><span style="color: rgb(27, 27, 27); background-color: rgb(251, 233, 230); font-size: 16px;">get </span><span style="color: rgb(27, 27, 27); font-size: 16px;">: 属性的 getter 函数，如果没有 getter，则为 </span>undefined<span style="color: rgb(27, 27, 27); font-size: 16px;">。当访问该属性时，会调用此函数。执行时不传入任何参数，但是会传入 </span>this<span style="color: rgb(27, 27, 27); font-size: 16px;"> 对象（由于继承关系，这里的</span>this<span style="color: rgb(27, 27, 27); font-size: 16px;">并不一定是定义该属性的对象）。该函数的返回值会被用作属性的值。 </span><span style="color: rgb(27, 27, 27); font-size: 16px;"><code><strong>默认为 undefined</strong></code></span><span style="color: rgb(27, 27, 27); font-size: 16px;">。</span></li><li><span style="color: rgb(27, 27, 27); background-color: rgb(251, 233, 230); font-size: 16px;">set :</span><span style="color: rgb(27, 27, 27); font-size: 16px;"> 属性的 setter 函数，如果没有 setter，则为 </span>undefined<span style="color: rgb(27, 27, 27); font-size: 16px;">。当属性值被修改时，会调用此函数。该方法接受一个参数（也就是被赋予的新值），会传入赋值时的 </span>this<span style="color: rgb(27, 27, 27); font-size: 16px;"> 对象。 </span><span style="color: rgb(27, 27, 27); font-size: 16px;"><code><strong>默认为 undefined</strong></code></span><span style="color: rgb(27, 27, 27); font-size: 16px;">。</span></li></ul><h3><span style="color: rgb(27, 27, 27); font-size: 16px;">尝试一下：</span></h3>`
);
const code2 = ref(`const data={
  name:"lzk",
  age:20,
}

// 变成响应式数据
observe(data);

function observe(target){
  if(typeof target !== 'object'||target === null){
    return target
  }
  for(let key in target){
    defineReactive(target,key,target[key])
  }
}

function defineReactive(target,key,value){
  Object.defineProperty(target,key,{
    get(){
      return value;
    },
    set(newValue){
      if(newValue !== value){
        value = newValue;
        // 更新视图
        console.log("更新视图");
      }
    }
  })
}

data.name = '李增坤'
console.log(data.name);  //更新视图;李增坤 
`);
const code3 = ref(`const object1 = {};

Object.defineProperty(object1, 'property1', {
  value: 42,
  writable: false
});

object1.property1 = 77;
// throws an error in strict mode

console.log(object1.property1);
// expected output: 42`);
const code4 = ref(`const data = {
  name: "lzk",
  age: 20,
  friend: {
    friendName: "nn",
  },
};

// 变成响应式数据
observe(data);

function observe(target) {
  if (typeof target !== "object" || target === null) {
    return target;
  }
  for (let key in target) {
    defineReactive(target, key, target[key]);
  }
}

function defineReactive(target, key, value) {
  // 深度观察
  observe(value);

  Object.defineProperty(target, key, {
    get() {
      return value;
    },
    set(newValue) {
      // 如果 newVlue 是对象，也要监听它
      observe(value);
      if (newValue !== value) {
        value = newValue;
        // 更新视图
        console.log("更新视图");
      }
    },
  });
}

data.friend.friendName = "丁真"; //更新视图
data.age = { number: 22 }; //更新视图
data.age.number = 21; //更新视图;更新视图`);
const code5 = ref(`const data = {
  name: "lzk",
  age: 20,
  friend: {
    friendName: "nn",
  },
  colors:['red','green','blue']
};

// 把需要监听的数组转成对象
const oldArrayProto = Array.prototype;
const newArrayProto = Object.create(oldArrayProto);
['push','pop','shilt','unshift','splice','sort','reverse'].forEach(methodName=>{
  newArrayProto[methodName] = function(){
    // 更新视图
    console.log('更新视图...');
    // 执行原本的功能
    oldArrayProto[methodName].call(this,...arguments)
  }
})
// 变成响应式数据
observe(data);

function observe(target) {
  if (typeof target !== "object" || target === null) {
    return target;
  }
  if(Array.isArray(target)){
    target.__proto__ = newArrayProto
  }
  for (let key in target) {
    defineReactive(target, key, target[key]);
  }
}

function defineReactive(target, key, value) {
  // 深度观察
  observe(value);

  Object.defineProperty(target, key, {
    get() {
      return value;
    },
    set(newValue) {
      // 如果 newVlue 是对象，也要监听它
      observe(value);
      if (newValue !== value) {
        value = newValue;
        // 更新视图
        console.log("更新视图");
      }
    },
  });
}

data.colors.push('black') //更新视图`);



const data = {
  name: "lzk",
  age: 20,
  friend: {
    friendName: "nn",
  },
  colors: ["red", "green", "blue"],
};

// 把需要监听的数组转成对象
const oldArrayProto = Array.prototype;
const newArrayProto = Object.create(oldArrayProto);
["push", "pop", "shilt", "unshift", "splice"].forEach((methodName) => {
  newArrayProto[methodName] = function () {
    // 更新视图
    console.log("更新视图...");
    // 执行原本的功能
    oldArrayProto[methodName].call(this, ...arguments);
  };
});
// 变成响应式数据
observe(data);

function observe(target) {
  if (typeof target !== "object" || target === null) {
    return target;
  }
  if (Array.isArray(target)) {
    target.__proto__ = newArrayProto;
  }
  for (let key in target) {
    defineReactive(target, key, target[key]);
  }
}

function defineReactive(target, key, value) {
  // 深度观察
  observe(value);

  Object.defineProperty(target, key, {
    get() {
      return value;
    },
    set(newValue) {
      // 如果 newVlue 是对象，也要监听它
      observe(value);
      if (newValue !== value) {
        value = newValue;
        // 更新视图
        console.log("更新视图");
      }
    },
  });
}

data.colors.push("black"); //更新视图
</script>

<style lang="less" scoped>
#content {
  position: relative;
}
</style>
